﻿using System;
using System.Collections.Generic;
using System.Linq;
using Jayden.Dll.Cards.Enums;
using Jayden.Dll.Cards.Poker.Enums;

namespace Jayden.Dll.Cards.Poker
{
	public class PokerHighHand : IComparable<PokerHighHand>
	{
		private uint m_Value;
		private PokerHandType m_Type;
		private CardRank[] m_Ranks;
		private CardRank[] m_Kickers;

		public static PokerHighHand GetHand(CardSet cards)
		{
			uint result = CardBit.Evaluate(cards.Bitset, cards.Count);
			return new PokerHighHand(result);
			/*
			if(cards == null || cards.Count > 5)
				throw new ArgumentException("Invalid count.", "card");
			ulong rankMask = 0x1 | (0x1 << 13) | (0x1 << 26) | (0x1 << 39) ;
			int four = -1;
			int three = -1;
			int pair1 = -1;
			int pair2 = -1;
			HashSet<CardRank> kickers = new HashSet<CardRank>();
			CardSet aces = new CardSet(rankMask << 12);
			aces.Keep(cards);
			int straightCount = (aces.Count > 0) ? 1 : 0;
			int straightValue = -1;
			for (int c = 0; c < 13; c++)
			{
				CardSet ranks = new CardSet(rankMask << c);
				ranks.Keep(cards);
				int count = ranks.Count;
				if (count == 4)
					four = c;
				else if (count == 3)
					three = c;
				else if (count == 2)
				{
					if (pair1 == -1)
						pair1 = c;
					else
						pair2 = c;
				}
				else if (count == 1)
					kickers.Add((CardRank)c);
				if (count == 1)
				{
					straightCount++;
					if (straightCount == 5)
						straightValue = c;
				}
				else
					straightCount = 0;
			}
			if (pair1 < pair2)
			{
				int tmp = pair2;
				pair2 = pair1;
				pair1 = tmp;
			}
			if (four != -1)
				return new PokerHighHand(PokerHandType.FourOfAKind, kickers, (CardRank)four);
			if (three != -1)
			{
				if (pair1 != -1)
					return new PokerHighHand(PokerHandType.FullHouse, null, (CardRank)three, (CardRank)pair1);
				return new PokerHighHand(PokerHandType.ThreeOfAKind, kickers, (CardRank)three);
			}
			if (pair1 != -1)
			{
				if (pair2 != -1)
					return new PokerHighHand(PokerHandType.TwoPairs, kickers, (CardRank)pair1, (CardRank)pair2);
				return new PokerHighHand(PokerHandType.OnePair, kickers, (CardRank)pair1);
			}
			CardSet spades = new CardSet(cards);
			CardSet hearts = new CardSet(cards);
			CardSet clubs = new CardSet(cards);
			CardSet diamonds = new CardSet(cards);
			spades.Keep(CardSet.Spades);
			hearts.Keep(CardSet.Hearts);
			clubs.Keep(CardSet.Clubs);
			diamonds.Keep(CardSet.Diamonds);
			bool suited = spades.Count == 5 || hearts.Count == 5 || clubs.Count == 5 || diamonds.Count == 5;
			if (straightValue != -1)
			{
				if (suited)
					return new PokerHighHand(PokerHandType.StraightFlush, null, (CardRank)straightValue);
				return new PokerHighHand(PokerHandType.Straight, null, (CardRank)straightValue);
			}
			if(suited)
				return new PokerHighHand(PokerHandType.Flush, kickers);
			return new PokerHighHand(PokerHandType.HighCard, kickers);
			*/
		}
		public static PokerHighHand GetHand(params Card[] cards)
		{
			return GetHand(new CardSet(cards));
			/*
			if (cards == null || cards.Length > 5)
				throw new ArgumentException("Invalid count.", "card");
			HashSet<CardRank> ranks = new HashSet<CardRank>();
			int[] countRanks = new int[Enum.GetValues(typeof(CardRank)).Length];
			int[] countSuits = new int[Enum.GetValues(typeof(CardSuit)).Length];
			bool[] straight = new bool[countRanks.Length];
			bool haveSuit = false;
			List<CardRank> pairs = new List<CardRank>();
			CardRank? three = null;
			CardRank? four = null;
			for (int c = 0; c < cards.Length; c++)
			{
				CardRank rank = cards[c].Rank;
				CardSuit suit = cards[c].Suit;
				int rankIndex = (int)rank;
				int suitIndex = (int)suit;
				countRanks[rankIndex]++;
				countSuits[suitIndex]++;
				straight[rankIndex] = true;
				if (countSuits[suitIndex] >= 5)
					haveSuit = true;
				int count = countRanks[rankIndex];
				if(count == 1)
					ranks.Add(rank);
				else if (count == 2)
				{
					ranks.Remove(rank);
					pairs.Add(rank);
				}
				else if (countRanks[rankIndex] == 3)
				{
					pairs.Remove(rank);
					three = rank;
				}
				else if (countRanks[rankIndex] == 4)
				{
					three = null;
					four = rank;
				}
			}
			int straightHighestRank = -1;
			int countStraight = straight[straight.Length - 1] ? 1 : 0;
			for (int c = 0; c < straight.Length; c++)
			{
				if (straight[c])
				{
					countStraight++;
					if (countStraight >= 5)
						straightHighestRank = c;
				}
				else
					countStraight = 0;
			}
			if (straightHighestRank > -1 && haveSuit)
				return new PokerHighHand(PokerHandType.StraightFlush, null, (CardRank)straightHighestRank);
			if (four.HasValue)
				return new PokerHighHand(PokerHandType.FourOfAKind, ranks, four.Value);
			if (three.HasValue && pairs.Count == 1)
				return new PokerHighHand(PokerHandType.FullHouse, ranks, three.Value, pairs[0]);
			if (haveSuit)
				return new PokerHighHand(PokerHandType.Flush, ranks);
			if (straightHighestRank > -1)
				return new PokerHighHand(PokerHandType.Straight, null, (CardRank)straightHighestRank);
			if (three.HasValue)
				return new PokerHighHand(PokerHandType.ThreeOfAKind, ranks, three.Value);
			pairs.Sort();
			if (pairs.Count == 2)
				return new PokerHighHand(PokerHandType.TwoPairs, ranks, pairs[1], pairs[0]);
			if (pairs.Count == 1)
				return new PokerHighHand(PokerHandType.OnePair, ranks, pairs[0]);
			return new PokerHighHand(PokerHandType.HighCard, ranks);
			*/
		}
		public static PokerHighHand GetBestHand(CardList holeCards, CardList communityCards = null)
		{
			HashSet<Card> cards = new HashSet<Card>(holeCards);
			if (communityCards != null)
				foreach (Card c in communityCards)
					cards.Add(c);
			return GetBestHand(cards);
		}
		public static PokerHighHand GetBestHand(params CardSet[] cardSets)
		{
			CardSet hashset = new CardSet();
			foreach (CardSet set in cardSets)
				hashset.Add(set);
			return GetBestHand(hashset);
		}
		public static PokerHighHand GetBestHand(CardSet cards)
		{
			uint result = CardBit.Evaluate(cards.Bitset, cards.Count);
			return new PokerHighHand(result);
			/*
			if (cards.Count <= 5)
				return GetHand(cards);
			List<ulong> list = new List<ulong>(cards.Masks);
			PokerHighHand bestHand = null;
			for (int c0 = 0; c0 < list.Count - 4; c0++)
			{
				for (int c1 = c0 + 1; c1 < list.Count - 3; c1++)
				{
					for (int c2 = c1 + 1; c2 < list.Count - 2; c2++)
					{
						for (int c3 = c2 + 1; c3 < list.Count - 1; c3++)
						{
							for (int c4 = c3 + 1; c4 < list.Count; c4++)
							{
								PokerHighHand hand = GetHand(new CardSet(list[c0] | list[c1] | list[c2] | list[c3] | list[c4]));
								if (bestHand == null || hand.CompareTo(bestHand) < 0)
									bestHand = hand;
							}
						}
					}
				}
			}
			return bestHand;
			*/
		}
		public static PokerHighHand GetBestHand(HashSet<Card> cards)
		{
			return GetBestHand(new CardSet(cards));
			/*
			if (cards.Count <= 5)
				return GetHand(cards.ToArray());
			List<Card> list = new List<Card>(cards);
			PokerHighHand bestHand = null;
			for (int c0 = 0; c0 < list.Count - 4; c0++)
			{
				for (int c1 = c0 + 1; c1 < list.Count - 3; c1++)
				{
					for (int c2 = c1 + 1; c2 < list.Count - 2; c2++)
					{
						for (int c3 = c2 + 1; c3 < list.Count - 1; c3++)
						{
							for (int c4 = c3 + 1; c4 < list.Count; c4++)
							{
								PokerHighHand hand = GetHand(list[c0], list[c1], list[c2], list[c3], list[c4]);
								if (bestHand == null || hand.CompareTo(bestHand) < 0)
									bestHand = hand;
							}
						}
					}
				}
			}
			return bestHand;
			*/
		}
		public static Dictionary<string, double> GetEquity(Dictionary<string, CardSet> playerCards, CardSet communityCards)
		{
			if (communityCards == null)
				communityCards = new CardSet();
			CardSet excluded = new CardSet();
			foreach (KeyValuePair<string, CardSet> pair in playerCards)
				excluded.Add(pair.Value);
			Dictionary<string, double> equity = new Dictionary<string, double>();
			foreach (KeyValuePair<string, CardSet> pair in playerCards)
				equity.Add(pair.Key, 0);
			equity.Add(string.Empty, 0);

			foreach (ulong commuity in CardBit.Hands(communityCards.Bitset, excluded.Bitset, 5))
			{
				HashSet<string> winners = new HashSet<string>();
				uint bestHand = 0;
				foreach (KeyValuePair<string, CardSet> pair in playerCards)
				{
					uint result = CardBit.Evaluate(pair.Value.Bitset | commuity, 7);
					if (bestHand < result)
					{
						bestHand = result;
						winners.Clear();
						winners.Add(pair.Key);
					}
					else if (bestHand == result)
						winners.Add(pair.Key);
				}
				foreach (string winner in winners)
					equity[winner]++;
				equity[string.Empty]++;
			}

			double total = equity[string.Empty];
			equity.Remove(string.Empty);
			HashSet<string> keys = new HashSet<string>(playerCards.Keys);
			foreach (string key in keys)
				equity[key] /= total;
			return equity;
		}
		/*
		private static void ComputeEquity(Dictionary<string, CardSet> playerCards, CardSet communityCards, CardSet excluded, Dictionary<string, double> equity)
		{
			if (communityCards.Count < 5)
			{
				CardSet newExcluded = new CardSet(excluded);
				CardSet cardSet = new CardSet(CardSet.All);
				cardSet.Remove(excluded);
				cardSet.Remove(communityCards);
				foreach (Card card in cardSet.Cards)
				{
					CardSet newCommunity = new CardSet(communityCards);
					newCommunity.Add(card);
					ComputeEquity(playerCards, newCommunity, newExcluded, equity);
					newExcluded.Add(card);
				}
			}
			else
			{
				HashSet<string> winners = new HashSet<string>();
				PokerHighHand bestHand = null;
				foreach (KeyValuePair<string, CardSet> pair in playerCards)
				{
					PokerHighHand highHand = GetBestHand(pair.Value, communityCards);
					if (bestHand == null)
					{
						bestHand = highHand;
						winners.Add(pair.Key);
					}
					else
					{
						int compare = bestHand.CompareTo(highHand);
						if (compare == 0)
							winners.Add(pair.Key);
						else if (compare > 0)
						{
							bestHand = highHand;
							winners.Clear();
							winners.Add(pair.Key);
						}
					}
				}
				foreach (string winner in winners)
					equity[winner] += 1;
				equity[string.Empty] += 1;
			}
		}
		*/
		public PokerHighHand(PokerHandType type, HashSet<CardRank> kickers, params CardRank[] ranks)
		{
			m_Type = type;
			m_Ranks = ranks;
			if(kickers != null && kickers.Count > 0)
			{
				List<CardRank> kickersSorted = new List<CardRank>(kickers);
				kickersSorted.Sort(delegate(CardRank t1, CardRank t2) { return -t1.CompareTo(t2); });
				m_Kickers = kickersSorted.ToArray();
			}
			List<CardRank> allRanks = new List<CardRank>();
			if (m_Ranks != null)
				foreach (CardRank rank in m_Ranks)
					allRanks.Add(rank);
			if (m_Kickers != null)
				foreach (CardRank rank in m_Kickers)
					allRanks.Add(rank);
			m_Value = (uint)m_Type << CardBit.HandTypeOffset;
			if (allRanks.Count > 0)
			{
				m_Value += (uint)allRanks[0] << CardBit.TopCardOffset;
				if (allRanks.Count > 1)
				{
					m_Value += (uint)allRanks[1] << CardBit.SecondCardOffset;
					if (allRanks.Count > 2)
					{
						m_Value += (uint)allRanks[2] << CardBit.ThirdCardOffset;
						if (allRanks.Count > 3)
						{
							m_Value += (uint)allRanks[3] << CardBit.FourthCardOffset;
							if(allRanks.Count > 4)
								m_Value += (uint)allRanks[4] << CardBit.FifthCardOffset;
						}
					}
				}
			}
		}
		public PokerHighHand(uint pokerhand)
		{
			m_Value = pokerhand;
			m_Type = (PokerHandType)((uint)PokerHandType.HighCard - (pokerhand >> CardBit.HandTypeOffset));
			uint[] cards = new uint[5];
			cards[0] = (pokerhand >> CardBit.TopCardOffset) & CardBit.CardMask;
			cards[1] = (pokerhand >> CardBit.SecondCardOffset) & CardBit.CardMask;
			cards[2] = (pokerhand >> CardBit.ThirdCardOffset) & CardBit.CardMask;
			cards[3] = (pokerhand >> CardBit.FourthCardOffset) & CardBit.CardMask;
			cards[4] = (pokerhand >> CardBit.FifthCardOffset) & CardBit.CardMask;
			switch(m_Type)
			{
				case PokerHandType.Straight:
				case PokerHandType.StraightFlush:
					m_Ranks = new CardRank[]{ (CardRank)(cards[0]) };
					break;
				case PokerHandType.FullHouse:
					m_Ranks = new CardRank[]{ (CardRank)cards[0], (CardRank)cards[1] };
					break;
				case PokerHandType.TwoPairs:
					m_Ranks = new CardRank[]{ (CardRank)cards[0], (CardRank)cards[1] };
					m_Kickers = new CardRank[]{ (CardRank)cards[2]};
					break;
				case PokerHandType.ThreeOfAKind:
					m_Ranks = new CardRank[]{ (CardRank)cards[0] };
					m_Kickers = new CardRank[]{ (CardRank)cards[1], (CardRank)cards[2]};
					break;
				case PokerHandType.FourOfAKind:
					m_Ranks = new CardRank[]{ (CardRank)cards[0] };
					m_Kickers = new CardRank[]{ (CardRank)cards[1]};
					break;
				case PokerHandType.Flush:
				case PokerHandType.HighCard:
					m_Kickers = new CardRank[]{ (CardRank)cards[0], (CardRank)cards[1], (CardRank)cards[2], (CardRank)cards[3], (CardRank)cards[4]};
					break;
				case PokerHandType.OnePair:
					m_Ranks = new CardRank[] { (CardRank)cards[0] };
					m_Kickers = new CardRank[] { (CardRank)cards[1], (CardRank)cards[2], (CardRank)cards[3] };
					break;
				default: throw new NotImplementedException();
			}
		}

		public PokerHandType Type { get { return m_Type; } }
		public CardRank[] Ranks { get { return m_Ranks; } }
		public CardRank[] Kickers { get { return m_Kickers; } }

		public int CompareTo(PokerHighHand other)
		{
			return -m_Value.CompareTo(other.m_Value);
			/*
			int compare = m_Type.CompareTo(other.m_Type);
			if (compare == 0)
			{
				if (m_Ranks != null && m_Ranks.Length > 0)
				{
					for (int c = 0; c < m_Ranks.Length; c++)
					{
						compare = m_Ranks[c].CompareTo(other.m_Ranks[c]);
						if (compare != 0)
							return -compare;
					}
				}
				if (m_Kickers != null && m_Kickers.Length > 0)
				{
					for (int c = 0; c < m_Kickers.Length; c++)
					{
						compare = m_Kickers[c].CompareTo(other.m_Kickers[c]);
						if (compare != 0)
							return -compare;
					}
				}
			}
			return compare;
			*/
		}
		public override string ToString()
		{
			List<string> strings = new List<string>();
			strings.Add(m_Type.ToString());
			if(m_Ranks != null && m_Ranks.Length > 0)
				strings.Add(string.Join(",", m_Ranks));
			if (m_Kickers != null && m_Kickers.Length > 0)
				strings.Add(string.Join(",", m_Kickers));
			return string.Join(" ", strings);
		}

		public string Description
		{
			get
			{
				switch (m_Type)
				{
					case PokerHandType.StraightFlush:
						return string.Format("straight flush, {0} high", m_Ranks[0]);
					case PokerHandType.FourOfAKind:
						return string.Format("four of a kind, {0}", Card.GetRankPairName(m_Ranks[0]));
					case PokerHandType.FullHouse:
						return string.Format("full house, {0} full of {1}", Card.GetRankPairName(m_Ranks[0]), Card.GetRankPairName(m_Ranks[1]));
					case PokerHandType.Flush:
						return string.Format("flush, {0} high", m_Kickers[0]);
					case PokerHandType.Straight:
						return string.Format("straight, {0} high", m_Ranks[0]);
					case PokerHandType.ThreeOfAKind:
						return string.Format("three of a kind, {0}", Card.GetRankPairName(m_Ranks[0]));
					case PokerHandType.TwoPairs:
						return string.Format("two pair, {0} and {1}", Card.GetRankPairName(m_Ranks[0]), Card.GetRankPairName(m_Ranks[1]));
					case PokerHandType.OnePair:
						return string.Format("pair of {0}", Card.GetRankPairName(m_Ranks[0]));
					case PokerHandType.HighCard:
						return string.Format("high card {0}", m_Kickers[0]);
				}
				return string.Empty;
			}
		}
	}
}
